package com.ljx.combinationMode;

import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * 组合模式（透明组合）
 *
 * 定义：
 * 又名部分整体模式，是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象，
 * 用来表示部分以及整体层次。这种类型的设计模式属于结构型模式，它创建了对象组的树形结构
 *
 * 在使用组合模式时，根据抽象构件类的定义形式，我们可将组合模式分为透明组合模式和安全组合模式两种形式。
 *  * 透明组合模式
 * 透明组合模式中，抽象根节点角色中声明了所有用于管理成员对象的方法，比如在示例中 `MenuComponent` 声明了 `add`、`remove` 、`getChild` 方法，
 * 这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。
 * 透明组合模式的缺点是不够安全，因为叶子对象和容器对象在本质上是有区别的，叶子对象不可能有下一个层次的对象，即不可能包含成员对象，
 * 因此为其提供 add()、remove() 等方法是没有意义的，这在编译阶段不会出错，但在运行阶段如果调用这些方法可能会出错（如果没有提供相应的错误处理代码）
 * * 安全组合模式
 * 在安全组合模式中，在抽象构件角色中没有声明任何用于管理成员对象的方法，而是在树枝节点 `Menu` 类中声明并实现这些方法。
 * 安全组合模式的缺点是不够透明，因为叶子构件和容器构件具有不同的方法，且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义，
 * 因此客户端不能完全针对抽象编程，必须有区别地对待叶子构件和容器构件。
 *
 * 优点：
 * 组合模式可以清楚地定义分层次的复杂对象，表示对象的全部或部分层次，它让客户端忽略了层次的差异，方便对整个层次结构进行控制。
 * 客户端可以一致地使用一个组合结构或其中单个对象，不必关心处理的是单个对象还是整个组合结构，简化了客户端代码。
 * 在组合模式中增加新的树枝节点和叶子节点都很方便，无须对现有类库进行任何修改，符合“开闭原则”。
 * 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案，通过叶子节点和树枝节点的递归组合，可以形成复杂的树形结构，但对树形结构的控制却非常简单
 * 缺点：
 * 破坏了“单一职责原则”。
 * 在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些特定类型的对象，例如在某个文件夹中只能包含文本文件，
 * 使用组合模式时，不能依赖类型系统来施加这些约束，因为它们都来自于相同的抽象层，在这种情况下，必须通过在运行时进行类型检查来实现，这个实现过程较为复杂
 *
 * 使用场景：
 * 组合模式正是应树形结构而生，所以组合模式的使用场景就是出现树形结构的地方。比如：文件目录显示，多级目录呈现等树形结构数据的操作
 *
 * 经典案例：
 *
 * JDK中的HashMap
 * 抽象节点Map
 * 树枝节点HashMap
 * 叶子节点Node
 *
 * MyBatis中的SqlNode
 * 抽象节点SqlNode
 * 树枝节点TextSqlNode、WhereSqlNode、IfSqlNode等等
 *
 * @作者 LJX
 * @日期 2022/12/23 15:16
 */
public class CombinationTest {

    public static void main(String[] args) {

        //初始化菜单数据
        MenuContainer 菜单管理 = new MenuContainer("菜单管理", 2);
        菜单管理.add(new MenuItem("页面访问", 3));
        菜单管理.add(new MenuItem("展开菜单", 3));
        菜单管理.add(new MenuItem("编辑菜单", 3));
        菜单管理.add(new MenuItem("删除菜单", 3));
        菜单管理.add(new MenuItem("新增菜单", 3));

        MenuContainer 权限配置 = new MenuContainer("权限配置", 2);
        权限配置.add(new MenuItem("页面访问", 3));
        权限配置.add(new MenuItem("提交保存", 3));

        MenuContainer 角色管理 = new MenuContainer("角色管理", 2);
        角色管理.add(new MenuItem("角色列表", 3));
        角色管理.add(new MenuItem("新建角色", 3));
        角色管理.add(new MenuItem("删除角色", 3));

        MenuContainer 系统管理 = new MenuContainer("系统管理", 1);
        系统管理.add(菜单管理);
        系统管理.add(权限配置);
        系统管理.add(权限配置);

        //打印菜单，向下打印
        系统管理.print();

    }

}

/**
 *  菜单组件 （抽象根节点）
 */
@Data
abstract class MenuAssembly{

    protected String name;
    protected Integer level;

    public MenuAssembly(String name, Integer level) {
        this.name = name;
        this.level = level;
    }

    /**
     * 添加节点方法
     * 默认实现抛出异常，因为叶子节点不能添加节点
     * @param ma
     */
    public void add(MenuAssembly ma){
        throw new UnsupportedOperationException();
    }

    /**
     * 删除节点方法
     * 默认实现抛出异常，因为叶子节点不能删除节点
     * @param ma
     */
    public void remove(MenuAssembly ma){
        throw new UnsupportedOperationException();
    }

    /**
     * 获取节点方法
     * 默认实现抛出异常，因为叶子节点不能获取节点
     * @param ma
     */
    public void getNode(Integer level){
        throw new UnsupportedOperationException();
    }

    /**
     * 打印方法
     */
    public abstract void print();

}

/**
 * 菜单容器 （树枝节点）
 */
class MenuContainer extends MenuAssembly{
    //菜单容器
    private List<MenuAssembly> mas = new ArrayList<>();

    public MenuContainer(String name, Integer level) {
        super(name, level);
    }

    /**
     * 重写添加节点方法
     * @param ma
     */
    @Override
    public void add(MenuAssembly ma) {
        mas.add(ma);
    }

    /**
     * 重写移除节点方法
     * @param ma
     */
    @Override
    public void remove(MenuAssembly ma) {
        mas.remove(ma);
    }

    /**
     * 重写获取节点方法
     * @param level
     */
    @Override
    public void getNode(Integer level) {
        mas.get(level);
    }

    /**
     * 重写打印方法
     */
    @Override
    public void print() {
        for (int i = 0; i < level; i++) {
            System.out.print("--");
        }
        System.out.println(name);
        for (MenuAssembly ma : mas) {
            ma.print();
        }
    }
}

/**
 * 菜单个体（叶子节点）
 */
class MenuItem extends MenuAssembly{

    public MenuItem(String name, Integer level) {
        super(name, level);
    }

    @Override
    public void print() {
        for (int i = 0; i < level; i++) {
            System.out.print("--");
        }
        System.out.println(name);
    }
}
